package cloud.tianai.ram.policy.client.aop;

import cloud.tianai.ram.policy.client.CurrentUserStatementProvider;
import cloud.tianai.ram.policy.common.model.dto.Match;
import cloud.tianai.ram.policy.common.exception.RamException;
import cloud.tianai.ram.policy.common.handler.PermissionHandler;
import cloud.tianai.ram.policy.common.model.dto.Statement;
import cloud.tianai.ram.policy.common.model.dto.Action;
import cloud.tianai.ram.policy.client.ActionHolder;
import cloud.tianai.ram.policy.client.common.util.RamAnnotationUtils;
import cloud.tianai.ram.policy.client.RamContext;
import cloud.tianai.ram.policy.client.common.exception.RamHitException;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;

import static cloud.tianai.ram.policy.client.ActionHolder.*;

/**
 * @Author: 天爱有情
 * @date 2022/3/24 10:34
 * @Description ActionInterceptor @Action
 */
public class ActionInterceptor implements MethodInterceptor, Ordered {

    /**
     * method args name Discoverer
     */
    final ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
    /**
     * spel
     */
    final ExpressionParser expressionParser = new SpelExpressionParser();

    /**
     * ram权限匹配核心类
     */
    PermissionHandler permissionHandler;
    /**
     * 当前用户权限提供者
     */
    CurrentUserStatementProvider currentUserStatementProvider;
    /**
     * Action 持有者
     */
    ActionHolder actionHolder;

    public ActionInterceptor(PermissionHandler permissionHandler,
                             CurrentUserStatementProvider currentUserStatementProvider,
                             ActionHolder actionHolder) {
        this.permissionHandler = permissionHandler;
        this.currentUserStatementProvider = currentUserStatementProvider;
        this.actionHolder = actionHolder;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        Action action = actionHolder.getAction(invocation.getMethod());
        Assert.notNull(action, "can not find Action for method:" + invocation);

        Map<String, Object> expand = action.getExpand();
        // skip check!
        if ((Boolean) expand.get(SKIP)) {
            return invocation.proceed();
        }
        // resource
        String resourceStr = action.getResource();
        Assert.hasText(resourceStr, "resource is Null");

        // resource dynamic param
        resourceStr = replaceDynamicParam(resourceStr, invocation);

        // action
        String actionStr = action.getAction();
        Assert.hasText(actionStr, "action is Null");

        // autoMatch
        boolean autoMatch = (Boolean) expand.get(AUTO_MATCH);

        // hitThrowError
        boolean hitThrowError = (Boolean) expand.get(HIT_THROW_ERROR);
        // gen Action
        Action finalAction = action.copy();
        finalAction.setResource(resourceStr);
        // add Action to RamContext
        RamContext.setCurrentAction(finalAction);
        if (autoMatch) {
            // get current user statementList
            List<Statement> statementList = currentUserStatementProvider.getCurrentUserStatementList(invocation);
            // add statementList to RamContext
            RamContext.setCurrentStatement(statementList);
            // check!
            Match match = permissionHandler.handle(finalAction, statementList);
            if (match.isDeny() && hitThrowError) {
                throw new RamHitException(finalAction, match);
            }
            // add match to RamContext
            RamContext.setCurrentMatch(match);
        }
        try {
            return invocation.proceed();
        } finally {
            // clear data from RamContext
            RamContext.setCurrentAction(null);
            RamContext.setCurrentMatch(null);
            RamContext.setCurrentStatement(null);
        }

    }

    private String replaceDynamicParam(String resourceStr, MethodInvocation invocation) {
        List<String> resourceParams = RamAnnotationUtils.getResourceParams(resourceStr);
        if (!CollectionUtils.isEmpty(resourceParams)) {
            EvaluationContext context = new MethodBasedEvaluationContext(null, invocation.getMethod(), invocation.getArguments(), nameDiscoverer);
            for (String resourceParam : resourceParams) {
                try {
                    String key = expressionParser.parseExpression(resourceParam).getValue(context, String.class);
                    if (StringUtils.isEmpty(key)) {
                        throw new RamException("invocation:[" + invocation + "] spel[" + resourceParam + "] data is null");
                    }
                    resourceStr = resourceStr.replace(resourceParam, key);

                } catch (SpelEvaluationException ex) {
                    // spel解析错误
                    throw new RamException("resource[" + resourceParam + "] parse spel error: " + ex);
                }
            }
        }
        return resourceStr;
    }

    @Override
    public int getOrder() {
        return 0;
    }

}
